package com.shadow.demo.dictionary.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.prefs.Preferences;

/**
 * 注册表工具类
 *
 * @author penggch
 */
public class RegeditUtil {

    public static final int HKEY_CURRENT_USER = 0x80000001;
    public static final int HKEY_LOCAL_MACHINE = 0x80000002;
    public static final int REG_SUCCESS = 0;
    public static final int REG_NOTFOUND = 2;
    public static final int REG_ACCESSDENIED = 5;

    private static final int KEY_ALL_ACCESS = 0xf003f;
    private static final int KEY_READ = 0x20019;
    private static final Preferences userRoot = Preferences.userRoot();
    private static final Preferences systemRoot = Preferences.systemRoot();
    private static final Class<? extends Preferences> userClass = userRoot.getClass();
    private static final Logger log = LoggerFactory.getLogger(RegeditUtil.class);
    private static Method regOpenKey = null;
    private static Method regCloseKey = null;
    private static Method regQueryValueEx = null;
    private static Method regEnumValue = null;
    private static Method regQueryInfoKey = null;
    private static Method regEnumKeyEx = null;
    private static Method regCreateKeyEx = null;
    private static Method regSetValueEx = null;
    private static Method regDeleteKey = null;
    private static Method regDeleteValue = null;

    private static Method getMethodOfRegOpenKey() {
        if (regOpenKey == null) {
            regOpenKey = getMethod(userClass, "WindowsRegOpenKey", new Class[]{int.class, byte[].class, int.class});
            setAccessible(regOpenKey);
        }
        return regOpenKey;
    }

    private static Method getMethodOfRegCloseKey() {
        if (regCloseKey == null) {
            regCloseKey = getMethod(userClass, "WindowsRegCloseKey", new Class[]{int.class});
            setAccessible(regCloseKey);
        }
        return regCloseKey;
    }

    private static Method getMethodOfRegQueryValueEx() {
        if (regQueryValueEx == null) {
            regQueryValueEx = getMethod(userClass, "WindowsRegQueryValueEx", new Class[]{int.class, byte[].class});
            setAccessible(regQueryValueEx);
        }
        return regQueryValueEx;
    }

    private static Method getMethodOfRegEnumValue() {
        if (regEnumValue == null) {
            regEnumValue = getMethod(userClass, "WindowsRegEnumValue", new Class[]{int.class, int.class, int.class});
            setAccessible(regEnumValue);
        }
        return regEnumValue;
    }

    private static Method getMethodOfRegQueryInfoKey() {
        if (regQueryInfoKey == null) {
            regQueryInfoKey = getMethod(userClass, "WindowsRegQueryInfoKey1", new Class[]{int.class});
            setAccessible(regQueryInfoKey);
        }
        return regQueryInfoKey;
    }

    private static Method getMethodOfRegEnumKeyEx() {
        if (regEnumKeyEx == null) {
            regEnumKeyEx = getMethod(userClass, "WindowsRegEnumKeyEx", new Class[]{int.class, int.class, int.class});
            setAccessible(regEnumKeyEx);
        }
        return regEnumKeyEx;
    }

    private static Method getMethodOfRegCreateKeyEx() {
        if (regCreateKeyEx == null) {
            regCreateKeyEx = getMethod(userClass, "WindowsRegCreateKeyEx", new Class[]{int.class, byte[].class});
            setAccessible(regCreateKeyEx);
        }
        return regCreateKeyEx;
    }

    private static Method getMethodOfRegSetValueEx() {
        if (regSetValueEx == null) {
            regSetValueEx = getMethod(userClass, "WindowsRegSetValueEx",
                    new Class[]{int.class, byte[].class, byte[].class});
            setAccessible(regSetValueEx);
        }
        return regSetValueEx;
    }

    private static Method getMethodOfRegDeleteValue() {
        if (regDeleteValue == null) {
            regDeleteValue = getMethod(userClass, "WindowsRegDeleteValue", new Class[]{int.class, byte[].class});
            setAccessible(regDeleteValue);
        }
        return regDeleteValue;
    }

    private static Method getMethodOfRegDeleteKey() {
        if (regDeleteKey == null) {
            regDeleteKey = getMethod(userClass, "WindowsRegDeleteKey", new Class[]{int.class, byte[].class});
            setAccessible(regDeleteKey);
        }
        return regDeleteKey;
    }

    private static Method setAccessible(Method method) {
        ReflectUtil.makeAccessible(method);
        return method;
    }

    private static Method getMethod(Class<? extends Preferences> cls, String name, Class<?>... parameterTypes) {
        Method[] methods = cls.getDeclaredMethods();
        List<Method> list = new ArrayList<Method>();
        for (Method method : methods) {
            if (!name.equals(method.getName())) {
                continue;
            }
            Class<?>[] types = method.getParameterTypes();
            if (types != null && parameterTypes != null && parameterTypes.length != types.length) {
                continue;

            } else if ((types == null || types.length == 0) && parameterTypes != null && parameterTypes.length > 0) {
                continue;

            } else if (types != null && types.length > 0 && (parameterTypes == null || parameterTypes.length == 0)) {
                continue;
            }
            list.add(method);
        }
        if (list.size() == 0) {
            return null;
        } else if (list.size() == 1) {
            return list.get(0);
        } else {
            Method result = null;
            try {
                result = cls.getDeclaredMethod(name, parameterTypes);
            } catch (Exception e) {
                log.error("", e);
            }
            if (result == null) {
                result = list.get(0);
            }
            return result;
        }
    }

    /**
     * 读取注册表的值
     *
     * @param hkey      HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
     * @param key       注册表键
     * @param valueName 值名称
     * @return
     */
    public static String tryReadString(int hkey, String key, String valueName) {
        try {
            return readString(hkey, key, valueName);
        } catch (Exception e) {
            log.error("", e);
        }
        return null;
    }

    /**
     * 读取注册表的值
     *
     * @param hkey      HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
     * @param key       注册表键
     * @param valueName 值名称
     * @return
     */
    public static String readString(int hkey, String key, String valueName) {
        try {
            if (hkey == HKEY_LOCAL_MACHINE) {
                return readString(systemRoot, hkey, key, valueName);
            } else if (hkey == HKEY_CURRENT_USER) {
                return readString(userRoot, hkey, key, valueName);
            } else {
                throw new RuntimeException("无效的hkey, [hkey:" + hkey + "]");
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 读取注册表键的子项
     *
     * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
     * @param key  注册表键
     * @return
     */
    public static Map<String, String> readStringValues(int hkey, String key) {
        try {
            if (hkey == HKEY_LOCAL_MACHINE) {
                return readStringValues(systemRoot, hkey, key);
            } else if (hkey == HKEY_CURRENT_USER) {
                return readStringValues(userRoot, hkey, key);
            } else {
                throw new RuntimeException("无效的hkey, [hkey:" + hkey + "]");
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 读取注册表键的子键
     *
     * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
     * @param key  注册表键
     * @return
     */
    public static List<String> readStringSubKeys(int hkey, String key) {
        try {
            if (hkey == HKEY_LOCAL_MACHINE) {
                return readStringSubKeys(systemRoot, hkey, key);
            } else if (hkey == HKEY_CURRENT_USER) {
                return readStringSubKeys(userRoot, hkey, key);
            } else {
                throw new RuntimeException("无效的hkey, [hkey:" + hkey + "]");
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 创建一个注册表键
     *
     * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
     * @param key  注册表键
     */
    public static void createKey(int hkey, String key) {
        try {
            int[] ret;
            if (hkey == HKEY_LOCAL_MACHINE) {
                ret = createKey(systemRoot, hkey, key);
                regCloseKey.invoke(systemRoot, new Object[]{Integer.valueOf(ret[0])});
            } else if (hkey == HKEY_CURRENT_USER) {
                ret = createKey(userRoot, hkey, key);
                regCloseKey.invoke(userRoot, new Object[]{Integer.valueOf(ret[0])});
            } else {
                throw new RuntimeException("无效的hkey, [hkey:" + hkey + "]");
            }
            if (ret[1] != REG_SUCCESS) {
                throw new RuntimeException("创建失败，[rc:" + ret[1] + "] [key:" + key + "]");
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }

    }

    /**
     * 给某项设置值
     *
     * @param hkey      HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
     * @param key       注册表键
     * @param valueName 值名称
     * @param value     值
     */
    public static void writeStringValue(int hkey, String key, String valueName, String value) {
        try {
            if (hkey == HKEY_LOCAL_MACHINE) {
                writeStringValue(systemRoot, hkey, key, valueName, value);
            } else if (hkey == HKEY_CURRENT_USER) {
                writeStringValue(userRoot, hkey, key, valueName, value);
            } else {
                throw new RuntimeException("无效的hkey, [hkey:" + hkey + "]");
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 删除一个键
     *
     * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
     * @param key  注册表键
     */
    public static void deleteKey(int hkey, String key) {
        try {
            int rc = -1;
            if (hkey == HKEY_LOCAL_MACHINE) {
                rc = deleteKey(systemRoot, hkey, key);
            } else if (hkey == HKEY_CURRENT_USER) {
                rc = deleteKey(userRoot, hkey, key);
            }
            if (rc != REG_SUCCESS) {
                throw new RuntimeException("删除失败，[rc:" + rc + "] [key:" + key + "]");
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 删除一个键的值
     *
     * @param hkey      HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
     * @param key       注册表键
     * @param valueName 值名称
     */
    public static void deleteValue(int hkey, String key, String valueName) {
        try {
            int rc = -1;
            if (hkey == HKEY_LOCAL_MACHINE) {
                rc = deleteValue(systemRoot, hkey, key, valueName);
            } else if (hkey == HKEY_CURRENT_USER) {
                rc = deleteValue(userRoot, hkey, key, valueName);
            }
            if (rc != REG_SUCCESS) {
                throw new RuntimeException("删除失败，[rc:" + rc + "] [key:" + key + "] [value:" + valueName + "]");
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    private static int deleteValue(Preferences root, int hkey, String key, String value)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Method methodOfRegOpenKey = getMethodOfRegOpenKey();
        int[] handles = (int[]) invoke(root, methodOfRegOpenKey, int[].class, Integer.valueOf(hkey), toCstr(key),
                Integer.valueOf(KEY_ALL_ACCESS));

        if (handles[1] != REG_SUCCESS) {
            return handles[1];
        }
        Method methodOfRegDeleteValue = getMethodOfRegDeleteValue();
        int rc = ((Integer) invoke(root, methodOfRegDeleteValue, Integer.class, Integer.valueOf(handles[0]),
                toCstr(value))).intValue();

        Method methodOfRegCloseKey = getMethodOfRegCloseKey();
        invoke(root, methodOfRegCloseKey, Integer.valueOf(handles[0]));

        return rc;
    }

    private static int deleteKey(Preferences root, int hkey, String key)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Method methodOfRegDeleteKey = getMethodOfRegDeleteKey();
        int rc = ((Integer) invoke(root, methodOfRegDeleteKey, Integer.class, Integer.valueOf(hkey), toCstr(key)))
                .intValue();
        return rc;
    }

    private static String readString(Preferences root, int hkey, String key, String value)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Method methodOfRegOpenKey = getMethodOfRegOpenKey();
        int[] handles = (int[]) invoke(root, methodOfRegOpenKey, int[].class, Integer.valueOf(hkey), toCstr(key),
                Integer.valueOf(KEY_READ));
        if (handles[1] != REG_SUCCESS) {
            return null;
        }
        Method methodOfRegQueryValueEx = getMethodOfRegQueryValueEx();
        byte[] valb = (byte[]) invoke(root, methodOfRegQueryValueEx, byte[].class, Integer.valueOf(handles[0]),
                toCstr(value));

        Method methodOfRegCloseKey = getMethodOfRegCloseKey();
        invoke(root, methodOfRegCloseKey, Integer.valueOf(handles[0]));

        return (valb != null ? new String(valb).trim() : null);
    }

    private static Map<String, String> readStringValues(Preferences root, int hkey, String key)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        HashMap<String, String> results = new HashMap<String, String>();
        Method methodOfRegOpenKey = getMethodOfRegOpenKey();
        int[] handles = (int[]) invoke(root, methodOfRegOpenKey, int[].class, Integer.valueOf(hkey), toCstr(key),
                Integer.valueOf(KEY_READ));

        if (handles[1] != REG_SUCCESS) {
            return null;
        }
        Method methodOfRegQueryInfoKey = getMethodOfRegQueryInfoKey();
        int[] info = (int[]) invoke(root, methodOfRegQueryInfoKey, int[].class, Integer.valueOf(handles[0]));

        int count = info[0];
        int maxlen = info[3];
        Method methodOfRegEnumValue = getMethodOfRegEnumValue();
        for (int index = 0; index < count; index++) {
            byte[] name = (byte[]) invoke(root, methodOfRegEnumValue, byte[].class, Integer.valueOf(handles[0]),
                    Integer.valueOf(index), Integer.valueOf(maxlen + 1));
            String value = readString(hkey, key, new String(name));
            results.put(new String(name).trim(), value);
        }

        Method methodOfRegCloseKey = getMethodOfRegCloseKey();
        invoke(root, methodOfRegCloseKey, Integer.valueOf(handles[0]));
        return results;
    }

    private static List<String> readStringSubKeys(Preferences root, int hkey, String key)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        List<String> results = new ArrayList<String>();
        Method methodOfRegOpenKey = getMethodOfRegOpenKey();
        int[] handles = (int[]) invoke(root, methodOfRegOpenKey, int[].class, Integer.valueOf(hkey), toCstr(key),
                Integer.valueOf(KEY_READ));

        if (handles[1] != REG_SUCCESS) {
            return null;
        }

        Method methodOfRegQueryInfoKey = getMethodOfRegQueryInfoKey();
        int[] info = (int[]) invoke(root, methodOfRegQueryInfoKey, int[].class, Integer.valueOf(handles[0]));

        int count = info[0];
        int maxlen = info[3];
        Method methodOfRegEnumKeyEx = getMethodOfRegEnumKeyEx();
        for (int index = 0; index < count; index++) {
            byte[] name = (byte[]) invoke(root, methodOfRegEnumKeyEx, byte[].class, Integer.valueOf(handles[0]),
                    Integer.valueOf(index), Integer.valueOf(maxlen + 1));
            results.add(new String(name).trim());
        }

        Method methodOfRegCloseKey = getMethodOfRegCloseKey();
        invoke(root, methodOfRegCloseKey, Integer.valueOf(handles[0]));
        return results;
    }

    private static int[] createKey(Preferences root, int hkey, String key)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Method methodOfRegCreateKeyEx = getMethodOfRegCreateKeyEx();
        return (int[]) invoke(root, methodOfRegCreateKeyEx, int[].class, Integer.valueOf(hkey), toCstr(key));
    }

    private static void writeStringValue(Preferences root, int hkey, String key, String valueName, String value)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Method methodOfRegOpenKey = getMethodOfRegOpenKey();
        int[] handles = (int[]) invoke(root, methodOfRegOpenKey, int[].class, Integer.valueOf(hkey), toCstr(key),
                Integer.valueOf(KEY_ALL_ACCESS));

        Method methodOfRegSetValueEx = getMethodOfRegSetValueEx();
        invoke(root, methodOfRegSetValueEx, Integer.valueOf(handles[0]), toCstr(valueName), toCstr(value));

        Method methodOfRegCloseKey = getMethodOfRegCloseKey();
        invoke(root, methodOfRegCloseKey, Integer.valueOf(handles[0]));
    }

    private static byte[] toCstr(String str) {
        byte[] result = new byte[str.length() + 1];

        for (int i = 0; i < str.length(); i++) {
            result[i] = (byte) str.charAt(i);
        }
        result[str.length()] = 0;
        return result;
    }

    private static Object invoke(Object obj, Method method, Class<?> returnType, Object... parameterValues)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (method == null) {
            return null;
        }
        Object result = null;
        if (parameterValues == null || parameterValues.length == 0) {
            result = method.invoke(obj);
        } else {
            Class<?>[] types = method.getParameterTypes();
            for (int i = 0; i < parameterValues.length; i++) {
                Object value = parameterValues[i];
                if (value == null
                        || value.getClass().getName().equals(types[i].getName())
                        || types[i].isAssignableFrom(value.getClass())) {
                    continue;
                } else {
                    value = parseValue(value, types[i]);
                    parameterValues[i] = value;
                }
            }
            result = method.invoke(obj, parameterValues);
            if (returnType != null && result != null && !returnType.getName().equals(result.getClass().getName())) {
                result = parseValue(result, returnType);
            }
        }
        return result;
    }

    private static void invoke(Object obj, Method method, Object... parameterValues)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (method == null) {
            return;
        }
        if (parameterValues == null || parameterValues.length == 0) {
            method.invoke(obj);
        } else {
            Class<?>[] types = method.getParameterTypes();
            for (int i = 0; i < parameterValues.length; i++) {
                Object value = parameterValues[i];
                if (value == null) {
                    continue;
                }
                if (value.getClass().getName().equals(types[i].getName())) {
                    continue;
                } else if (types[i].isAssignableFrom(value.getClass())) {
                    continue;
                } else {
                    value = parseValue(value, types[i]);
                    parameterValues[i] = value;
                }
            }
            method.invoke(obj, parameterValues);
        }
    }

    private static Object parseValue(Object value, Class<?> type) {
        if (type.getName().equals(long[].class.getName())) {
            int length = Array.getLength(value);
            long[] result = new long[length];
            for (int i = 0; i < length; i++) {
                result[i] = Long.parseLong(Array.get(value, i).toString());
            }
            value = result;

        } else if (type.getName().equals(long.class.getName())) {
            return Long.parseLong(value.toString());

        } else if (type.getName().equals(Long.class.getName())) {
            return Long.valueOf(value.toString());

        } else if (type.getName().equals(int[].class.getName())) {
            int length = Array.getLength(value);
            int[] result = new int[length];
            for (int i = 0; i < length; i++) {
                result[i] = Integer.parseInt(Array.get(value, i).toString());
            }
            value = result;

        } else if (type.getName().equals(int.class.getName())) {
            return Integer.parseInt(value.toString());

        } else if (type.getName().equals(Integer.class.getName())) {
            return Integer.valueOf(value.toString());

        } else if (type.getName().equals(byte[].class.getName())) {
            int length = Array.getLength(value);
            byte[] result = new byte[length];
            for (int i = 0; i < length; i++) {
                result[i] = Byte.parseByte(Array.get(value, i).toString());
            }
            value = result;

        } else if (type.getName().equals(byte.class.getName())) {
            return Byte.parseByte(value.toString());

        } else if (type.getName().equals(Byte.class.getName())) {
            return Byte.valueOf(value.toString());

        }
        return value;
    }

}
